#ifndef _UTIL_
#define _UTIL_

#include"log.hpp"

#include<iostream>
#include<ios>                   //ios
#include<sstream>               //stringstream;
#include<fstream>               //ifstream;

#include<string>
#include<string_view>
#include<memory>
#include<unordered_map>
#include<functional>
#include<vector>
#include<cassert>
#include<mutex>
#include<list>
#include<thread>
#include<condition_variable>

#include<websocketpp/server.hpp>
#include<websocketpp/config/asio_no_tls.hpp>
#include<sw/redis++/redis++.h>
#include<mysql/mysql.h>
#include<jsoncpp/json/json.h>

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

// mysql接口封装
class Mysql_Util
{
public:
    // 创建mysql连接
    static MYSQL* mysql_create(const std::string &host,
                               const std::string &user,
                               const std::string &pass,
                               const std::string &name,
                               uint16_t port)
    {
        // 初始化mysql句柄
        MYSQL *mysql=mysql_init(nullptr);
        if(mysql==nullptr)
        {
            ERR_LOG("init mysql instance failed!");
            return nullptr;
        }

        // 连接mysql服务
        if(mysql_real_connect(mysql,        //mysql_init()函数的返回值
                              host.c_str(), //mysql服务器的主机地址, 写IP地址
                              user.c_str(), //连接mysql服务器的用户名, 默认: root
                              pass.c_str(), //连接mysql服务器用户对应的密码, root用户的密码
                              name.c_str(), //要使用的数据库的名字
                              port,         //连接的mysql服务器监听的端口,默认端口3306
                              nullptr,      //本地套接字, 不使用指定为 NULL
                              0)==nullptr)  //通常为0
        {
            ERR_LOG("connect mysql server failed:%s",mysql_error(mysql));
            mysql_close(mysql);
            return nullptr;
        }
        
        // 设置字符集为utf-8
        mysql_set_character_set(mysql,"utf8");
        return mysql;
    }

    // 关闭mysql连接
    static void mysql_destroy(MYSQL* mysql)
    {
        if(mysql!=nullptr)
        {
            mysql_close(mysql);
        }
        return;
    }

    // 执行sql语句
    static bool mysql_exec(MYSQL* mysql,const std::string &sql)
    {
        int ret=mysql_query(mysql,sql.c_str());
        if(ret!=0)
        {
            ERR_LOG("execute [%s] failed:%s",sql.c_str(),mysql_error(mysql));
            return false;
        }
        return true;
    }
};

// redis接口封装
class Redis_Util
{
public:
    //创建redis连接
    static sw::redis::Redis* redis_connect(const std::string &host,uint16_t port)
    {
        sw::redis::ConnectionOptions opts;
        opts.host=host;
        opts.port=port;
        opts.db=3;
        opts.socket_timeout=std::chrono::milliseconds(500);
        sw::redis::Redis* redis=new sw::redis::Redis(opts);

        // std::string str="tcp://"+host+":"+std::to_string(port);
        // sw::redis::Redis redis(str);
        if(redis->ping("1")!="1")
        {
            ERR_LOG("connect redis server failed!");
            return nullptr;
        }
        DBG_LOG("redis connect");
        return redis;
    }

    static bool redis_create(sw::redis::Redis* redis,Json::Value &value)
    {
        std::string k(value["name"].asString());
        std::unordered_map<std::string,std::string> v;
        v["id"]=value["id"].asString();
        v["account"]=value["account"].asString();
        v["password"]=value["password"].asString();
        v["score"]=value["score"].asString();
        v["total_count"]=value["total_count"].asString();
        v["win_count"]=value["win_count"].asString();
        redis->hmset(k,v.begin(),v.end());

        redis->setnx(value["account"].asString(),value["name"].asString());
        redis->setnx(value["id"].asString(),value["name"].asString());

        auto ret=redis->hget(k,"account");
        if(ret)
        {
            DBG_LOG("redis create data");
            return true;
        }
        else return false;
    }

    static int redis_retrieve(sw::redis::Redis* redis,std::string key,std::string field,std::string ss)
    {
        DBG_LOG("redis retrieve1:%s,%s",key.c_str(),field.c_str());
        std::string s;
        std::string str;
        auto out=redis->get(key);
        if(out)
        {
            str=*out;
            DBG_LOG("redis retrieve1:%s",str.c_str());
            auto password=redis->hget(str,field);
            if(password)
            {
                s=*password;
                DBG_LOG("redis retrieve2:%s",s.c_str());
            }
        }
        DBG_LOG("redis retrieve3");
        if(s==ss)
        {
            auto id=redis->hget(str,"id");
            std::string i=*id;
            return std::stoi(i);
        }
        return 0;
    }

    static bool redis_retrieve_by_name(sw::redis::Redis* redis,const std::string &key,Json::Value &value)
    {
        std::string id=redis->hget(key,"id").value();
        std::string score=redis->hget(key,"score").value();
        std::string total_count=redis->hget(key,"total_count").value();
        std::string win_count=redis->hget(key,"win_count").value();
        DBG_LOG("redis retrieve_by_name data:%s,%s,%s,%s,%s",id.c_str(),key.c_str(),score.c_str(),total_count.c_str(),win_count.c_str());
        value["id"]=std::stoi(id);
        value["name"]=key;
        value["score"]=std::stoi(score);
        value["total_count"]=std::stoi(total_count);
        value["win_count"]=std::stoi(win_count);
        if(!value["id"].empty()&&!value["name"].empty()&&!value["score"].empty()&&!value["total_count"].empty()&&!value["win_count"].empty())
        {
            DBG_LOG("redis retrieve data");
            return true;
        }
        else return false;
    }

    static bool redis_retrieve_by_id(sw::redis::Redis* redis,const std::string &key,Json::Value &value)
    {
        auto out=redis->get(key);
        std::string name1=*out;
        DBG_LOG("redis retrieve_by_id data:%s",name1.c_str());
        return Redis_Util::redis_retrieve_by_name(redis,name1,value);
    }

    static void redis_update(sw::redis::Redis* redis,const std::string &key,bool res)
    {
        Json::Value value;
        Redis_Util::redis_retrieve_by_id(redis,key,value);

        std::string k(value["name"].asString());
        std::unordered_map<std::string,std::string> v;
        if(res)v["score"]=std::to_string(value["score"].asUInt()+30);
        else v["score"]=std::to_string(value["score"].asUInt()-20);
        v["total_count"]=std::to_string(value["total_count"].asUInt()+1);
        if(res)v["win_count"]=std::to_string(value["win_count"].asUInt()+1);
        
        redis->hset(k,v.begin(),v.end());

        DBG_LOG("redis update data");
    }
};

// json接口封装
class Json_Util
{
public:
    // 序列化
    static bool serialize(const Json::Value &value,std::string &str)
    {
        // 使用StreamWriterBuilder创建一个StreamWriter对象，并用std::unique_ptr管理它的生命周期
        // 工厂模式典型应用，目的是：解耦对象的创建和使用，并自动管理内存，避免内存泄漏
        Json::StreamWriterBuilder swb;
        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

        // 将value内容序列化，并写入到ss中
        std::stringstream ss;
        int ret=sw->write(value,&ss);
        if(ret!=0)
        {
            ERR_LOG("json serialize failed!");
            return false;
        }
        str=ss.str();
        return true;
    } 

    // 反序列化
    static bool unserialize(const std::string &str,Json::Value &value)
    {
        Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

        // 将str内容反序列化，并写入到value
        // parse第一个参数str开始位置，第二个参数str结束位置
        bool ret=cr->parse(str.c_str(),str.c_str()+str.size(),&value,nullptr);
        DBG_LOG("str:%s",str.c_str());
        if(!ret)
        {
            ERR_LOG("json unserialize failed!");
            return false;
        }
        return true;
    }
};

// 切割字符串工具
class String_Util
{
public:
    // 切割工具
    // 参数传入要切割的字符串，分隔符，存放切割后字符串的数组
    // 返回值返回切割后字符串个数
    static int split(const std::string &in,const std::string &sep,std::vector<std::string> &arry)
    {
        arry.clear();
        size_t pos,idx=0;
        while(idx<in.size())
        {
            pos=in.find(sep,idx);
            if(pos==std::string::npos)
            {
                arry.push_back(in.substr(idx));
                break;
            }
            if(pos!=idx)
            {
                arry.push_back(in.substr(idx,pos-idx));
            }
            idx=pos+sep.size();
        }
        return arry.size();
    }
};

// 文件读取工具
class File_Util
{
public:
    // 读取工具
    // 读取文件内容到body中
    static bool read(const std::string &filename,std::string &body)
    {
        // 打开文件
        std::ifstream file;
        // in:读取 out:写入 app:追加 ate:定位到文件末尾 trunc:如果文件存在，先清空内容 binary:二进制模式打开 
        file.open(filename.c_str(),std::ios::in|std::ios::binary);
        if(!file)
        {
            ERR_LOG("file open failed!");
            return false;
        }

        // 计算文件大小
        // seekg：设置要从输入流中提取的下一个字符的位置
        // tellg：返回当前字符在输入流的位置
        // good：检查流是否足以正常工作
        file.seekg(0,std::ios::end);
        body.resize(file.tellg());
        file.seekg(0,std::ios::beg);
        file.read(&body[0],body.size());
        if(file.good()==false)
        {
            ERR_LOG("file read failed!");
            file.close();
            return false;
        }
        file.close();
        return true;
    }
};

#endif